package club.xyes.zkh.ecommerce.service.impl;

import club.xyes.zkh.ecommerce.commons.dto.DatetimeRange;
import club.xyes.zkh.ecommerce.commons.entity.*;
import club.xyes.zkh.ecommerce.commons.exception.BadRequestException;
import club.xyes.zkh.ecommerce.commons.exception.InternalServerErrorException;
import club.xyes.zkh.ecommerce.commons.util.RandomUtil;
import club.xyes.zkh.ecommerce.commons.vo.TransactionLogOverview;
import club.xyes.zkh.ecommerce.commons.vo.payment.PrePayResultVo;
import club.xyes.zkh.ecommerce.dao.mapper.TransactionLogMapper;
import club.xyes.zkh.ecommerce.dao.mapper.UserCouponMapper;
import club.xyes.zkh.ecommerce.service.TransactionLogService;
import club.xyes.zkh.ecommerce.service.adapter.impl.TransactionLogSingleDependencyMapperAdapterImpl;
import club.xyes.zkh.ecommerce.service.listener.OnPaymentSuccessListener;
import club.xyes.zkh.ecommerce.wechat.dto.SaoBeiNotifyVo;
import club.xyes.zkh.ecommerce.wechat.dto.SaoBeiPayResult;
import club.xyes.zkh.ecommerce.wechat.dto.SaoBeiRefundResult;
import club.xyes.zkh.ecommerce.wechat.dto.TradeQueryResult;
import club.xyes.zkh.ecommerce.wechat.service.SaoBeiService;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.IOException;
import java.util.*;

/**
 * Create by 郭文梁 2019/4/19 0019 18:20
 * TransactionLogServiceImpl
 * 交易记录先关业务行为实现
 *
 * @author 郭文梁
 * @data 2019/4/19 0019
 */
@Service
@Slf4j
public class TransactionLogServiceImpl extends TransactionLogSingleDependencyMapperAdapterImpl implements TransactionLogService {
    /**
     * 交易号末尾拼接6位字符串
     */
    private static final int INC_STR_LEN = 6;
    private static final String MICRO_PROGRAM_TRADE_NO_PREFIX = "";
    private static final String MICRO_PROGRAM_TRADE_NO_SUFFIX = "";
    private final TransactionLogMapper mapper;
    private final SaoBeiService saoBeiService;
    private final UserCouponMapper userCouponMapper;

    @Autowired
    public TransactionLogServiceImpl(TransactionLogMapper mapper,
                                     SaoBeiService saoBeiService,
                                     UserCouponMapper userCouponMapper) {
        super(mapper);
        this.mapper = mapper;
        this.saoBeiService = saoBeiService;
        this.userCouponMapper = userCouponMapper;
    }

    @Override
    public PageInfo<TransactionLog> findByUserId(Integer userId, Integer page, Integer rows) {
        return PageHelper.startPage(page, rows)
                .doSelectPageInfo(() -> mapper.selectByUserIdFetchAll(userId));
    }

    @Override
    public Map<String, Object> overview(Shop shop, DatetimeRange range) {
        Map<String, Object> res = new HashMap<>(5);
        Map<String, Object> amountCols = mapper.overviewByShopId(shop.getId(), range.getStart(), range.getEnd());
        Map<String, Object> countCols = mapper.selectTransactionCountAndUserCount(shop.getId());
        if (amountCols == null) {
            amountCols = Collections.emptyMap();
        }
        if (countCols == null) {
            countCols = Collections.emptyMap();
        }
        Object transactionCount = countCols.getOrDefault("t_count", 0);
        Object userCount = countCols.getOrDefault("u_count", 0);
        Object amount = amountCols.getOrDefault("amount", 0);
        res.put("transactionCount", transactionCount);
        res.put("userCount", userCount);
        res.put("amount", amount);
        res.put("wechat", amount);
        res.put("alipay", 0);
        return res;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public PrePayResultVo<SaoBeiPayResult> prePay(User user, Shop shop, int fee) throws Exception {
        String openId = user.getWxOpenId();
        String tradeNo = RandomUtil.currentDateTimeStringWithIncrement(MICRO_PROGRAM_TRADE_NO_PREFIX, MICRO_PROGRAM_TRADE_NO_SUFFIX, INC_STR_LEN);
        return doPay(openId, fee, fee, 0, tradeNo, user, shop, null, 0, null, 0);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public PrePayResultVo<SaoBeiPayResult> preferentialPay(User user,
                                                           Shop shop,
                                                           int totalFee,
                                                           int noDiscountAmount,
                                                           UserCoupon userCoupon,
                                                           int couponDiscount,
                                                           UserMembership membership,
                                                           int membershipDiscount) throws Exception {
        checkCouponStatus(userCoupon);
        int actualAmount = totalFee - couponDiscount - membershipDiscount;
        String openId = user.getWxOpenId();
        String tradeNo = RandomUtil.currentDateTimeStringWithIncrement(MICRO_PROGRAM_TRADE_NO_PREFIX, MICRO_PROGRAM_TRADE_NO_SUFFIX, INC_STR_LEN);
        log.debug("Dp Pay actualAmount={},={}", actualAmount, totalFee);
        PrePayResultVo<SaoBeiPayResult> saoBeiPayResultPrePayResultVo = doPay(openId, totalFee, actualAmount, noDiscountAmount, tradeNo, user, shop, userCoupon, couponDiscount, membership, membershipDiscount);
        if (userCoupon != null) {
            userCoupon.setStatus(UserCoupon.STATUS_USED);
            userCouponMapper.updateByPrimaryKey(userCoupon);
        }
        return saoBeiPayResultPrePayResultVo;
    }


    @Override
    public TransactionLog refreshStatus(TransactionLog log,
                                        Shop shop,
                                        OnPaymentSuccessListener onPaymentSuccessListener,
                                        Map<String, Object> context) throws Exception {
        if (log.getStatus() == TransactionLog.STATUS_PAID) {
            //若已支付成功 则不再继续查询 也不再继续执行支付成功的回调
            return log;
        }
        TradeQueryResult queryResult = saoBeiService.query(log.getTradeNo(), log.getOutTradeNo(), shop);
        if (queryResult == null || !queryResult.isSuccess()) {
            throw new InternalServerErrorException("Call trade query api error,response:" + queryResult);
        }
        log.setStatus(queryResult.getTransactionLogStatus());
        log.setQueryJson(queryResult.getSourceJson());
        if (log.getStatus() == TransactionLog.STATUS_PAID) {
            onPaymentSuccessListener.onPaymentSuccess(log, context);
        }
        return updateById(log);
    }

    @Override
    public PageInfo<TransactionLog> findByShopId(Integer shopId, Integer page, Integer rows) {
        return PageHelper.startPage(page, rows).doSelectPageInfo(() -> mapper.selectByShopId(shopId));
    }

    @Override
    public List<TransactionLog> searchByAmount(Integer shopId, Integer amount) {
        return mapper.selectByShopIdAndAmount(shopId, amount);
    }

    @Override
    public PageInfo<TransactionLog> searchByAmount(Integer shopId, Integer amount, Integer page, Integer rows) {
        return PageHelper.startPage(page, rows).doSelectPageInfo(() -> mapper.selectByShopIdAndAmount(shopId, amount));
    }

    @Override
    public TransactionLogOverview overviewByShopAndUser(Integer shopId, Integer userId) {
        return mapper.overviewByShopAndUser(shopId, userId);
    }

    @Override
    public PageInfo<TransactionLog> findByUserAndShop(Integer userId, Integer shopId, int page, int rows) {
        TransactionLog query = new TransactionLog();
        query.setUserId(userId);
        query.setShopId(shopId);
        return PageHelper.startPage(page, rows, "create_time desc").doSelectPageInfo(() -> mapper.select(query));
    }

    @Override
    public TransactionLog refund(TransactionLog transactionLog, Shop shop) throws IOException {
        if (transactionLog.getStatus() != TransactionLog.STATUS_PAID) {
            throw new BadRequestException("订单状态不合法");
        }
        String traceNo = RandomUtil.currentDateTimeString("", "");
        SaoBeiRefundResult refundResult = saoBeiService.refund(transactionLog.getOutTradeNo(), transactionLog.getActualAmountPaid(), traceNo, shop);
        if (refundResult.isSuccess()) {
            transactionLog.setStatus(TransactionLog.STATUS_REFUNDED);
            transactionLog.setRefundTraceNo(traceNo);
            return updateById(transactionLog);
        } else {
            log.debug("Refund fail {}", refundResult);
            throw new InternalServerErrorException("退款失败:" + refundResult.getReturnMsg());
        }
    }

    @Override
    public TransactionLog findByOutTradeNo(String outTradeNo) {
        TransactionLog query = new TransactionLog();
        query.setOutTradeNo(outTradeNo);
        return findOneByQuery(query);
    }

    @Override
    public Map<String, Object> onNotify(SaoBeiNotifyVo notifyVo) {
        Map<String, Object> res = new HashMap<>(2);
        if (notifyVo == null) {
            return notifyFail(res, "参数验证失败");
        }
        TransactionLog transactionLog = findByOutTradeNo(notifyVo.getOutTradeNo());
        if (transactionLog == null) {
            return notifyFail(res, "交易记录不存在");
        }
        transactionLog.setNotifyJson(notifyVo.getSourceJson());
        if (notifyVo.isSuccess()) {
            transactionLog.setStatus(TransactionLog.STATUS_PAID);
        }
        transactionLog = updateById(transactionLog);
        log.debug("Transaction notify success, {}, {}", transactionLog, notifyVo);
        res.put("return_code", "01");
        res.put("return_msg", "success");
        return null;
    }

    private Map<String, Object> notifyFail(Map<String, Object> res, String msg) {
        res.put("return_code", "02");
        res.put("return_msg", msg);
        return res;
    }

    private void checkCouponStatus(UserCoupon userCoupon) {
        if (userCoupon == null) {
            return;
        }
        if (!Objects.equals(userCoupon.getStatus(), UserCoupon.STATUS_ENABLE)) {
            throw new BadRequestException("优惠券状态非法");
        }
    }

    /**
     * 创建交易记录
     *
     * @param payResult prepay api调用结果 需确保调用成功
     * @return TransactionLog entity
     */
    private TransactionLog createTransactionLog(SaoBeiPayResult payResult,
                                                int fee,
                                                int actualAmount,
                                                int noDiscountAmount,
                                                String tradeNo,
                                                User user,
                                                Shop shop,
                                                UserCoupon userCoupon,
                                                int couponDiscount,
                                                UserMembership membership,
                                                int membershipDiscount) {
        TransactionLog log = new TransactionLog();
        log.setUser(user);
        log.setUserId(user.getId());
        log.setShop(shop);
        log.setShopId(shop.getId());
        log.setAmountPayable(fee);
        log.setActualAmountPaid(actualAmount);
        log.setNoDiscountAmount(noDiscountAmount);
        //未参与优惠券优惠
        log.setUserCouponId(userCoupon == null ? null : userCoupon.getId());
        log.setUserCoupon(userCoupon);
        log.setCouponDiscountAmount(couponDiscount);
        //未参与会员优惠
        log.setMembershipLevelId(membership == null ? null : membership.getMembershipLevelId());
        log.setLevel(membership == null ? null : membership.getLevel());
        log.setMembershipDiscountAmount(membershipDiscount);
        log.setTradeNo(payResult.getOutTradeNo());
        log.setOutTradeNo(tradeNo);
        log.setCreateJson(payResult.getSourceJson());
        //初始状态：创建
        log.setStatus(TransactionLog.STATUS_CREATE);
        return log;
    }

    /**
     * 执行支付操作 调用支付API
     *
     * @param openId             OpenId
     * @param fee                支付金额
     * @param actualAmount       实付金额
     * @param tradeNo            订单号
     * @param user               用户
     * @param shop               商户
     * @param userCoupon         用户优惠券
     * @param noDiscountAmount   不参与优惠金额
     * @param couponDiscount     优惠券优惠金额
     * @param membership         用户会员
     * @param membershipDiscount 会员优惠金额
     * @return 支付结果 + 交易记录
     * @throws IOException IO异常
     */
    private PrePayResultVo<SaoBeiPayResult> doPay(String openId,
                                                  int fee,
                                                  int actualAmount,
                                                  int noDiscountAmount,
                                                  String tradeNo,
                                                  User user,
                                                  Shop shop,
                                                  UserCoupon userCoupon,
                                                  int couponDiscount,
                                                  UserMembership membership,
                                                  int membershipDiscount) throws IOException {
        SaoBeiPayResult payResult = saoBeiService.microProgramPay(openId, actualAmount, tradeNo, shop);
        if (payResult.isSuccess()) {
            TransactionLog log = createTransactionLog(payResult, fee, actualAmount, noDiscountAmount, tradeNo, user, shop, userCoupon, couponDiscount, membership, membershipDiscount);
            //状态：等待支付
            log.setStatus(TransactionLog.STATUS_PENDING_PAYMENT);
            //保存交易记录
            TransactionLog resLog = save(log);
            return PrePayResultVo.of(resLog, payResult);
        } else {
            throw new InternalServerErrorException("Call prepay api error:" + payResult.getSourceJson());
        }
    }
}
